// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ValidationAspects.Sdk.Interception
{
	public static class GetInterceptionValidationMethods
	{
		public static IEnumerable<MethodInfo> ForType(Type type, bool attributedMethodsOnly)
		{
			Enforce.IsNotNull(type, "type");

			foreach (MethodInfo methodInfo in
				type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Where(
					m => !m.IsAbstract && !m.Name.StartsWith("get_") && !m.Name.StartsWith("set_") && (m.DeclaringType != typeof(object))))
			{
				// check object validators have not been defined on static methods
				if (!methodInfo.CanValidate())
				{
					if (methodInfo.IsDefined(typeof(ValidatorAttribute), true))
						throw new ValidationException(string.Format("Object Validation cannot be applied to [method:{0}] [type:{1}]. Method is static or abstract.", methodInfo, methodInfo.DeclaringType));

					if (methodInfo.IsAbstract || methodInfo.GetParameters().Where(p => !p.IsOut).Count() == 0 || methodInfo.IsDefined(typeof(ValidationMethodAttribute), true))
						continue;
				}

				// check validators have not been defined on out parameters
				foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
				{
					if (!parameterInfo.CanValidate())
					{
						if (parameterInfo.IsDefined(typeof(ValidatorAttribute), true))
						{
							throw new ValidationException(string.Format("Validation cannot be applied to out parameter [name:{0}] [method:{1}] [type:{2}]. Parameter must not declare the out modifier.", parameterInfo.Name,
							                                            methodInfo.Name, methodInfo.DeclaringType));
						}

						continue;
					}
				}

				ParameterInfo[] parameters = methodInfo.GetParameters().Where(p => !p.IsOut).ToArray();

				// only validate methods decorated with a ValidatorAttribute or with parameters decorated with a ValidatorAttribute
				bool validateMethod = true;
				if (attributedMethodsOnly && !methodInfo.IsDefined(typeof(ValidatorAttribute), true))
				{
					validateMethod = false;
					foreach (ParameterInfo parameterInfo in parameters)
					{
						if (!parameterInfo.IsDefined(typeof(ValidatorAttribute), true))
							continue;

						validateMethod = true;
						break;
					}
				}

				if (!validateMethod)
					continue;

				yield return methodInfo;
			}
		}
	}
}